﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using AntC.AspNetCore.ApiEncryption.Internal;
using AntC.AspNetCore.ApiEncryption.Internal.Interfaces;
using AntC.AspNetCore.ApiEncryption.Internal.Streams;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace AntC.AspNetCore.ApiEncryption
{
    /// <summary>
    /// Api数据加密中间件
    /// </summary>
    public class ApiEncryptionMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="next"></param>
        /// <param name="loggerFactory"></param>
        public ApiEncryptionMiddleware(
            RequestDelegate next,
            ILoggerFactory loggerFactory)
        {
            if (next == null)
            {
                throw new ArgumentNullException(nameof(next));
            }
            if (loggerFactory == null)
            {
                throw new ArgumentNullException(nameof(loggerFactory));
            }

            _next = next;
            _logger = loggerFactory.CreateLogger<ApiEncryptionMiddleware>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="encryptProvider"></param>
        /// <param name="policyProvider"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext httpContext,
            IEncryptProvider encryptProvider,
            IApiEncryptionPolicyProvider policyProvider,
            IOptionsSnapshot<ApiEncryptionOptions> options)
        {
            var context = new ApiEncryptionContext(httpContext, _logger, encryptProvider, policyProvider, options);
            try
            {
                await DecryptApiData(context);

                if (context.ApiEncryptionOptions.ApiEncrypt && context.PolicyProvider.IsEncryptCurrentResponse(context))
                {
                    ShimApiEncryptionResponseStream(context);
                }

                // 执行其他中间件
                await _next(httpContext);

                await EncryptApiData(context);
            }
            catch (Exception ex)
            {
                throw;
            }
            finally
            {
                UnShimApiEncryptionRequestStream(context);
                await UnShimApiEncryptionResponseStreamAsync(context);
            }
        }

        private void ShimApiEncryptionRequestStream(ApiEncryptionContext context)
        {
            //启用读取request
            context.HttpContext.Request.EnableBuffering();

            context.OriginalRequestStream = context.HttpContext.Request.Body;

            var stream = new ApiEncryptStream();
            context.HttpContext.Request.Body = stream;

            context.ApiEncryptRequestStream = stream;
        }

        private void ShimApiEncryptionResponseStream(ApiEncryptionContext context)
        {
            context.ResponseContentType = context.HttpContext.Response.ContentType;
            //设置stream存放ResponseBody
            context.OriginalResponseStream = context.HttpContext.Response.Body;

            var stream = new ApiEncryptStream();
            context.HttpContext.Response.Body = stream;
            context.ApiEncryptResponseStream = stream;
        }

        private void UnShimApiEncryptionRequestStream(ApiEncryptionContext context)
        {

        }

        private async Task UnShimApiEncryptionResponseStreamAsync(ApiEncryptionContext context)
        {
            if (context.ApiEncryptResponseStream == null)
            {
                return;
            }

            context.ApiEncryptResponseStream.Position = 0;
            await context.ApiEncryptResponseStream.CopyToAsync(context.OriginalResponseStream);
            context.HttpContext.Response.Body = context.OriginalResponseStream;
            context.ApiEncryptResponseStream.Close();
            await context.ApiEncryptResponseStream.DisposeAsync();
            context.ApiEncryptResponseStream = null;
        }

        private async Task DecryptApiData(ApiEncryptionContext context)
        {
            var request = context.HttpContext.Request;

            #region 解密请求数据
            if (context.ApiEncryptionOptions.ApiDecrypt
                && context.PolicyProvider.IsDecryptCurrentRequest(context)
                && request.ContentLength != null
                && request.ContentLength > 0)
            {
                ShimApiEncryptionRequestStream(context);

                try
                {
                    #region 读取请求数据

                    //原始请求body
                    using var requestReader = new StreamReader(context.OriginalRequestStream);
                    var requestBody = await requestReader.ReadToEndAsync();
                    context.OriginalRequestStream.Position = 0;

                    #endregion

                    #region 解密请求数据

                    var decryptRequestBody = await context.EncryptProvider.Decrypt(context, requestBody);
                    await context.ApiEncryptRequestStream.WriteAsync(decryptRequestBody);
                    context.ApiEncryptRequestStream.Position = 0;

                    #endregion

                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "解密接口消息失败");
                    throw;
                }
            }
            #endregion
        }

        /// <summary>
        /// 加密数据
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private async Task EncryptApiData(ApiEncryptionContext context)
        {
            if (context.ApiEncryptionOptions.ApiEncrypt && context.PolicyProvider.IsEncryptCurrentResponse(context))
            {
                try
                {
                    #region 读取其他中间件处理结果

                    //处理执行其他中间件后的ResponseBody
                    context.ApiEncryptResponseStream.Position = 0;
                    var responseBody = await context.ApiEncryptResponseStream.ReadStringAsync();
                    #endregion

                    #region 加密结果数据

                    var encryptResponseBody = await context.EncryptProvider.Encrypt(context, responseBody);

                    await using var msEncrypt = new ApiEncryptStream();
                    await msEncrypt.WriteAsync(encryptResponseBody);
                    context.ApiEncryptResponseStream.Close();
                    context.ApiEncryptResponseStream.Dispose();
                    context.ApiEncryptResponseStream = msEncrypt;

                    await UnShimApiEncryptionResponseStreamAsync(context);

                    #endregion
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "加密接口处理结果失败");
                    throw;
                }
            }
        }
    }
}
